package com.summo.demo.config.log;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import com.alibaba.fastjson.JSONObject;

import com.summo.demo.config.context.GlobalUserContext;
import com.summo.demo.dao.UserLogMapper;
import com.summo.demo.entity.UserLogDO;
import com.summo.demo.util.HttpContextUtil;
import com.summo.demo.util.IPUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Aspect
@Component
@Order(3)
public class LogAspect {

    @Autowired
    private UserLogMapper userLogMapper;

    @Pointcut("@annotation(com.summo.demo.config.log.Log)")
    public void pointcut() {
        // do nothing
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            Object result = null;
            //默认操作对象为-1L
            MethodSignature signature = (MethodSignature)joinPoint.getSignature();
            Method method = signature.getMethod();
            Log logAnnotation = method.getAnnotation(Log.class);
            UserLogDO log = new UserLogDO();
            if (logAnnotation != null) {
                // 注解上的描述
                log.setOperation(logAnnotation.methodDesc());
            }
            // 请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 请求的方法名
            String methodName = signature.getName();
            log.setMethod(className + "." + methodName + "()");
            // 请求的方法参数值
            Object[] args = joinPoint.getArgs();
            // 请求的方法参数名称
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = u.getParameterNames(method);
            if (args != null && paramNames != null) {
                StringBuilder params = new StringBuilder();
                params = handleParams(params, args, Arrays.asList(paramNames));
                log.setParams(params.toString());
            }
            log.setGmtCreate(Calendar.getInstance().getTime());
            long beginTime = System.currentTimeMillis();
            // 执行方法
            result = joinPoint.proceed();
            // 执行时长(毫秒)
            long time = System.currentTimeMillis() - beginTime;
            HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
            // 设置 IP 地址
            String ip = IPUtil.getIpAddr(request);
            log.setIp(ip);
            log.setTime(time);
            // 设置返回值
            log.setResponseText(JSONObject.toJSONString(result));

            log.setCreatorId(GlobalUserContext.getUserContext().getUserId());
            //保存操作记录到数据库中
            userLogMapper.insert(log);
            return result;
        } catch (Exception e) {
            log.error("保存操作日志发生异常", e);
            throw e;
        }

    }

    /**
     * 参数打印合理化
     *
     * @param params     参数字符串
     * @param args       参数列表
     * @param paramNames 参数名
     * @return
     */
    private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) {
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Map) {
                Set set = ((Map)args[i]).keySet();
                List<Object> list = new ArrayList<>();
                List<Object> paramList = new ArrayList<>();
                for (Object key : set) {
                    list.add(((Map)args[i]).get(key));
                    paramList.add(key);
                }
                return handleParams(params, list.toArray(), paramList);
            } else {
                if (args[i] instanceof Serializable) {
                    Class<?> aClass = args[i].getClass();
                    try {
                        aClass.getDeclaredMethod("toString", new Class[] {null});
                        // 如果不抛出 NoSuchMethodException 异常则存在 toString 方法 ，安全的 writeValueAsString ，否则 走 Object的
                        // toString方法
                        params.append(" ").append(paramNames.get(i)).append(": ").append(
                            JSONObject.toJSONString(args[i]));
                    } catch (NoSuchMethodException e) {
                        params.append(" ").append(paramNames.get(i)).append(": ").append(
                            JSONObject.toJSONString(args[i].toString()));
                    }
                } else if (args[i] instanceof MultipartFile) {
                    MultipartFile file = (MultipartFile)args[i];
                    params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
                } else {
                    params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
                }
            }
        }
        return params;
    }
}
